﻿using System;
using System.Diagnostics;
using System.Globalization;
using System.Text;
using Kent.Boogaart.HelperTrinity; 

namespace Kent.Boogaart.Gauntlet.Diagnostics
{
	/// <summary>
	/// An implementation of <see cref="ILog"/> that logs to a <see cref="TraceSource"/> instance.
	/// </summary>
	internal sealed class TraceSourceLog : ILog
	{
		private readonly TraceSource _traceSource;
		//accessing the correlation manager via Trace.CorrelationManager is not thread safe, so we need our own lock
		private static readonly object _correlationManagerLock = new object();
		//defines the accepted log levels - used to increase the performance of argument checking 
		private static readonly LogLevel[] _logLevels = new LogLevel[] { LogLevel.Critical, LogLevel.Error, LogLevel.Information, LogLevel.Verbose, LogLevel.Warning };

		internal TraceSource TraceSource
		{
			get
			{
				return _traceSource;
			}
		}

		public bool IsVerboseEnabled
		{
			get
			{
				return _traceSource.Switch.ShouldTrace(TraceEventType.Verbose);
			}
		}

		public bool IsInformationEnabled
		{
			get
			{
				return _traceSource.Switch.ShouldTrace(TraceEventType.Information);
			}
		}

		public bool IsWarningEnabled
		{
			get
			{
				return _traceSource.Switch.ShouldTrace(TraceEventType.Warning);
			}
		}

		public bool IsErrorEnabled
		{
			get
			{
				return _traceSource.Switch.ShouldTrace(TraceEventType.Error);
			}
		}

		public TraceSourceLog(string name)
		{
			_traceSource = new TraceSource(name);
		}

		public void Verbose(string message)
		{
			Write(LogLevel.Verbose, message);
		}

		public void Verbose(string format, params object[] args)
		{
			Write(LogLevel.Verbose, format, args);
		}

		public void Information(string message)
		{
			Write(LogLevel.Information, message);
		}

		public void Information(string format, params object[] args)
		{
			Write(LogLevel.Information, format, args);
		}

		public void Warning(string message)
		{
			Write(LogLevel.Warning, message);
		}

		public void Warning(string format, params object[] args)
		{
			Write(LogLevel.Warning, format, args);
		}

		public void Error(string message)
		{
			Write(LogLevel.Error, message);
		}

		public void Error(string format, params object[] args)
		{
			Write(LogLevel.Error, format, args);
		}

		public void Exception(Exception ex)
		{
			Exception(LogLevel.Error, ex);
		}

		public void Exception(Exception ex, string message)
		{
			Exception(LogLevel.Error, ex, message, null);
		}

		public void Exception(Exception ex, string format, params object[] args)
		{
			Exception(LogLevel.Error, ex, format, args);
		}

		public void Exception(LogLevel level, Exception ex)
		{
			Exception(level, ex, null);
		}

		public void Exception(LogLevel level, Exception ex, string message)
		{
			Exception(level, ex, message, null);
		}

		public void Exception(LogLevel level, Exception ex, string format, params object[] args)
		{
			ArgumentHelper.AssertNotNull(ex, "ex");
			ArgumentHelper.AssertEnumMember(level, "level");
			StringBuilder sb = new StringBuilder();
			
			if (format != null)
			{
				if (args != null)
				{
					format = string.Format(CultureInfo.InvariantCulture, format, args);
				}

				sb.AppendLine(format);
			}

			while (ex != null)
			{
				sb.Append(ex.GetType().FullName).Append(" : ").AppendLine(ex.Message);
				sb.AppendLine(ex.StackTrace);
				ex = ex.InnerException;
				
				if (ex != null)
				{
					sb.AppendLine("--- INNER EXCEPTION ---");
				}
			}

			Write(level, sb.ToString());
		}

		public IDisposable Performance(string message)
		{
			ArgumentHelper.AssertNotNullOrEmpty(message, "message", true);
			return new PerformanceLogger(this, message);
		}

		public IDisposable Performance(string format, params object[] args)
		{
			ArgumentHelper.AssertNotNull(format, "format");
			ArgumentHelper.AssertNotNull(args, "args");
			return Performance(string.Format(CultureInfo.InvariantCulture, format, args));
		}

		public bool ShouldWrite(LogLevel level)
		{
			ArgumentHelper.AssertEnumMember(level, "level", _logLevels);
			return _traceSource.Switch.ShouldTrace((TraceEventType)level);
		}

		public void Write(LogLevel level, string message)
		{
			ArgumentHelper.AssertEnumMember(level, "level", _logLevels);
			ArgumentHelper.AssertNotNull(message, "message");
			_traceSource.TraceEvent((TraceEventType)level, 0, message);
		}

		public void Write(LogLevel level, string format, params object[] args)
		{
			ArgumentHelper.AssertEnumMember(level, "level", _logLevels);
			ArgumentHelper.AssertNotNull(format, "format");
			ArgumentHelper.AssertNotNull(args, "args");
			_traceSource.TraceEvent((TraceEventType)level, 0, format, args);
		}

		public IDisposable PushLogicalOperation(string operationName)
		{
			ArgumentHelper.AssertNotNullOrEmpty(operationName, "operationName", true);
			
			lock (_correlationManagerLock)
			{
				System.Diagnostics.Trace.CorrelationManager.StartLogicalOperation(operationName);
			}

			return new LogicalOperationClosure(this);
		}

		public IDisposable PushLogicalOperation(string format, params object[] args)
		{
			ArgumentHelper.AssertNotNull(format, "format");
			ArgumentHelper.AssertNotNull(args, "args");
			return PushLogicalOperation(string.Format(CultureInfo.InvariantCulture, format, args));
		}

		public void PopLogicalOperation()
		{
			lock (_correlationManagerLock)
			{
				System.Diagnostics.Trace.CorrelationManager.StopLogicalOperation();
			}
		}

		private sealed class LogicalOperationClosure : IDisposable
		{
			private readonly TraceSourceLog _log;
			private bool _disposed;
		
			public LogicalOperationClosure(TraceSourceLog log)
			{
				_log = log;
			}

			public void Dispose()
			{
				if (!_disposed)
				{
					_disposed = true;
					_log.PopLogicalOperation();
				}
			}
		}

		private sealed class PerformanceLogger : IDisposable
		{
			private TraceSourceLog _log;
			private string _message;
			private Stopwatch _stopwatch;
			private bool _disposed;
			
			public PerformanceLogger(TraceSourceLog log, string message)
			{
				System.Diagnostics.Debug.Assert(log != null);
				System.Diagnostics.Debug.Assert(message != null);
				_log = log;
				_message = message;
				_stopwatch = Stopwatch.StartNew();
			}
			
			public void Dispose()
			{
				if (!_disposed)
				{
					_stopwatch.Stop();
					_disposed = true;
					_log.Information("'{0}' took {1} to execute ({2}ms)", _message, _stopwatch.Elapsed, _stopwatch.ElapsedMilliseconds);
				}
			}
		}
	}
}